﻿/*
   Copyright 2012 Dmitry Bratus

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using Flower.Services;
using Flower.Services.Data;

namespace Flower.Client
{
    /// <summary>
    /// A script to execute in a directory.
    /// </summary>
    public sealed class Script
    {
        public class Result
        {
            #region ParamsMapper

            private class ParamsMapper : IScriptParameterVisitor
            {
                private readonly Dictionary<string, object> _params;

                public ParamsMapper(Dictionary<string, object> paramsDict)
                {
                    _params = paramsDict;
                }

                public void Visit(BoolScriptParameter param)
                {
                    _params[param.Name] = param.Value;
                }

                public void Visit(IntScriptParameter param)
                {
                    _params[param.Name] = param.Value;
                }

                public void Visit(DoubleScriptParameter param)
                {
                    _params[param.Name] = param.Value;
                }

                public void Visit(StringScriptParameter param)
                {
                    _params[param.Name] = param.Value;
                }

                public void Visit(DirectoryEntryScriptParameter param)
                {
                    _params[param.Name] = param.Value;
                }

                public void Visit(BoolArrayScriptParameter param)
                {
                    _params[param.Name] = param.Value;
                }

                public void Visit(IntArrayScriptParameter param)
                {
                    _params[param.Name] = param.Value;
                }

                public void Visit(DoubleArrayScriptParameter param)
                {
                    _params[param.Name] = param.Value;
                }

                public void Visit(StringArrayScriptParameter param)
                {
                    _params[param.Name] = param.Value;
                }

                public void Visit(DirectoryEntryArrayScriptParameter param)
                {
                    _params[param.Name] = param.Value;
                }
            }

            #endregion

            private readonly Dictionary<string, object> _data = new Dictionary<string,object>();

            public Result(ICollection<ScriptParameter> result)
            {
                var mapper = new ParamsMapper(_data);

                foreach (ScriptParameter p in result)
                {
                    p.AcceptVisitor(mapper);
                }
            }

            /// <summary>
            /// Returns a value of a parameter converted to a specified type.
            /// </summary>
            /// <typeparam name="T">The type of the value.</typeparam>
            /// <param name="paramName">Parameter name.</param>
            /// <returns>A value of a parameter converted to a specified type.</returns>
            public T Get<T>(string paramName)
            {
                object result;

                if (_data.TryGetValue(paramName, out result))
                {
                    return (result != null) ? (T)result : default(T);
                }

                return default(T);
            }
        }

        private readonly IDirectory _directory;
        private readonly IList<ScriptParameter> _params;
        private string _text;

        internal Script(IDirectory directory)
        {
            _directory = directory;
            _params = new List<ScriptParameter>();
        }

        /// <summary>
        /// Sets the text of the script.
        /// </summary>
        /// <param name="text">The text of the script.</param>
        /// <returns>This script.</returns>
        public Script SetText(string text)
        {
            if (string.IsNullOrWhiteSpace(text))
            {
                throw new ArgumentNullException("text");
            }

            _text = text;
            return this;
        }

        private void CheckParameterExists(string name)
        {
            foreach (ScriptParameter p in _params)
            {
                if (p.Name == name)
                {
                    throw new InvalidOperationException(string.Format("Parameter {0} already exists.", name));
                }
            }
        }

        /// <summary>
        /// Adds bool parameter.
        /// </summary>
        /// <param name="name">Name of the attribute in the $in object.</param>
        /// <param name="value">Value of the parameter.</param>
        /// <returns>This script.</returns>
        public Script AddParameter(string name, bool value)
        {
            CheckParameterExists(name);

            _params.Add
            (
                new BoolScriptParameter
                {
                    Name = name,
                    Value = value
                }
            );

            return this;
        }

        /// <summary>
        /// Adds int parameter.
        /// </summary>
        /// <param name="name">Name of the attribute in the $in object.</param>
        /// <param name="value">Value of the parameter.</param>
        /// <returns>This script.</returns>
        public Script AddParameter(string name, int value)
        {
            CheckParameterExists(name);

            _params.Add
            (
                new IntScriptParameter
                {
                    Name = name,
                    Value = value
                }
            );

            return this;
        }

        /// <summary>
        /// Adds double parameter.
        /// </summary>
        /// <param name="name">Name of the attribute in the $in object.</param>
        /// <param name="value">Value of the parameter.</param>
        /// <returns>This script.</returns>
        public Script AddParameter(string name, double value)
        {
            CheckParameterExists(name);

            _params.Add
            (
                new DoubleScriptParameter
                {
                    Name = name,
                    Value = value
                }
            );

            return this;
        }

        /// <summary>
        /// Adds string parameter.
        /// </summary>
        /// <param name="name">Name of the attribute in the $in object.</param>
        /// <param name="value">Value of the parameter.</param>
        /// <returns>This script.</returns>
        public Script AddParameter(string name, string value)
        {
            CheckParameterExists(name);

            _params.Add
            (
                new StringScriptParameter
                {
                    Name = name,
                    Value = value
                }
            );

            return this;
        }

        /// <summary>
        /// Adds DirectoryEntry parameter.
        /// </summary>
        /// <param name="name">Name of the attribute in the $in object.</param>
        /// <param name="value">Value of the parameter.</param>
        /// <returns>This script.</returns>
        public Script AddParameter(string name, DirectoryEntry value)
        {
            CheckParameterExists(name);

            _params.Add
            (
                new DirectoryEntryScriptParameter
                {
                    Name = name,
                    Value = value
                }
            );

            return this;
        }

        /// <summary>
        /// Adds bool collection parameter.
        /// </summary>
        /// <param name="name">Name of the attribute in the $in object.</param>
        /// <param name="value">Value of the parameter.</param>
        /// <returns>This script.</returns>
        public Script AddParameter(string name, IEnumerable<bool> value)
        {
            CheckParameterExists(name);

            _params.Add
            (
                new BoolArrayScriptParameter
                {
                    Name = name,
                    Value = value.ToList()
                }
            );

            return this;
        }

        /// <summary>
        /// Adds int collection parameter.
        /// </summary>
        /// <param name="name">Name of the attribute in the $in object.</param>
        /// <param name="value">Value of the parameter.</param>
        /// <returns>This script.</returns>
        public Script AddParameter(string name, IEnumerable<int> value)
        {
            CheckParameterExists(name);

            _params.Add
            (
                new IntArrayScriptParameter
                {
                    Name = name,
                    Value = value.ToList()
                }
            );

            return this;
        }

        /// <summary>
        /// Adds double collection parameter.
        /// </summary>
        /// <param name="name">Name of the attribute in the $in object.</param>
        /// <param name="value">Value of the parameter.</param>
        /// <returns>This script.</returns>
        public Script AddParameter(string name, IEnumerable<double> value)
        {
            CheckParameterExists(name);

            _params.Add
            (
                new DoubleArrayScriptParameter
                {
                    Name = name,
                    Value = value.ToList()
                }
            );

            return this;
        }

        /// <summary>
        /// Adds string collection parameter.
        /// </summary>
        /// <param name="name">Name of the attribute in the $in object.</param>
        /// <param name="value">Value of the parameter.</param>
        /// <returns>This script.</returns>
        public Script AddParameter(string name, IEnumerable<string> value)
        {
            CheckParameterExists(name);

            _params.Add
            (
                new StringArrayScriptParameter
                {
                    Name = name,
                    Value = value.ToList()
                }
            );

            return this;
        }

        /// <summary>
        /// Adds DirectoryEntry collection parameter.
        /// </summary>
        /// <param name="name">Name of the attribute in the $in object.</param>
        /// <param name="value">Value of the parameter.</param>
        /// <returns>This script.</returns>
        public Script AddParameter(string name, IEnumerable<DirectoryEntry> value)
        {
            CheckParameterExists(name);

            _params.Add
            (
                new DirectoryEntryArrayScriptParameter
                {
                    Name = name,
                    Value = value.ToList()
                }
            );

            return this;
        }

        /// <summary>
        /// Executes the script and returns the $out object.
        /// </summary>
        /// <returns>The $out object mapped to the CLR types.</returns>
        public Result Execute()
        {
            if (string.IsNullOrWhiteSpace(_text))
            {
                throw new InvalidOperationException("The text of the script is not set.");
            }

            return new Result(_directory.ExecuteScript(_text, _params));
        }
    }
}
